初看题面,看到 $K$ 大我们可以想到主席树,但是连边却又符合 $LCT$ ,但是毕竟 $LCT$ 是不能支持 $K$ 大的,因为 $Splay$ 辅助树不是二叉查找树。
不过主席树我们可以大力启发式合并,合并的时候重建节点的倍增数组并且重新建立节点的权值线段树。这样子每个节点要被修改的期望次数为 $logn$ 次,那么时间复杂度就是 $O(nlog^2n)$ (貌似是的),这足以让我们过这道题了。
1.主席树如何上树
上树[手动滑稽]……
首先,对于节点 $u$ 的权值线段树,$ta$ 是由 $fa[u]$ 的权值线段树继承过来的,因为只是多了一个 $u$ ,所以主席树只是多增加了 $logn$ 个节点。
既然是从父亲节点继承过来的话,那么很显然我们可以在预处理倍增数组的时候顺便将主席树建好。
Code-builld:
1 | void dfs(int u,int f) { |
这个很容易理解,那么我们怎么暴力合并两颗树呢?
对于要合并的两颗树,我们选择将 $size$ 小的往 $size$ 大的并,对于给出的 $x,y$ ,我们先给 $x,y$ 连好边,然后将 $y$ 的权值线段树从 $x$ 更新,丢掉以前的。最后遍历 $y$ 的子树,更新其倍增数组和权值线段树即可。
至于 $size$ 的维护的话,我们可以找到 $x,y$ 所在的树的根。这个样子 $size$ 谁大谁小只需要判断 $x,y$ 所在的树的根的 $size$ 谁大谁小即可。我们在网下遍历 $y$ 的子树时每次都将$x$ 所在树的根的 $size$ 加一即可。
Code-merge:
1 | void merge(int rt,int u,int f) { |
然后差不多了,至于 $sta$ 的话,因为要查询所在树的根,为了提高效率我们可以将其作为并查集的形式。
还有一点,对于 $vis$ 数组,实际上我们建树的时候就直接用 $merge$ 好了,$vis$ 只是用来判重而已,因为是森林,有很多树。所以说我们可以不用 $dfs$ 就将初始形态的树建好。
Code-pre:
1 | for(int i=1;i<=n;++i) |
最后需要注意的就是主席树的空间要开很大,差不多是 $nlog^2n$ ,因为有很多结点。
Code:
1 |
|
本文标题:【题解】 [SDOI2013]森林 主席树+启发式合并 luoguP3302
文章作者:Qiuly
发布时间:2019年03月29日 - 00:00
最后更新:2019年03月30日 - 15:18
原始链接:http://qiulyblog.github.io/2019/03/29/[题解]luoguP3302/
许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。